/*
Continuous Testing Toolkit
Copyright (C) 2009 Paul R. Holser, Jr.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package org.infinitest.toolkit.internal.reflection;

import static java.lang.reflect.Modifier.*;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author <a href="http://www.pholser.com">Paul Holser</a>
 */
class ClassUtilities {
    private static final Map<Class<?>, Class<?>> objectToPrimitiveMap = new HashMap<Class<?>, Class<?>>(13);

    static {
        objectToPrimitiveMap.put(Boolean.class, Boolean.TYPE);
        objectToPrimitiveMap.put(Byte.class, Byte.TYPE);
        objectToPrimitiveMap.put(Character.class, Character.TYPE);
        objectToPrimitiveMap.put(Double.class, Double.TYPE);
        objectToPrimitiveMap.put(Float.class, Float.TYPE);
        objectToPrimitiveMap.put(Integer.class, Integer.TYPE);
        objectToPrimitiveMap.put(Long.class, Long.TYPE);
        objectToPrimitiveMap.put(Short.class, Short.TYPE);
    }

    private static final Map<Class<?>, Set<Class<?>>> primitiveWideningsMap = new HashMap<Class<?>, Set<Class<?>>>(11);

    static {
        Set<Class<?>> set = new HashSet<Class<?>>();
        set.add(Short.TYPE);
        set.add(Integer.TYPE);
        set.add(Long.TYPE);
        set.add(Float.TYPE);
        set.add(Double.TYPE);
        primitiveWideningsMap.put(Byte.TYPE, set);

        set = new HashSet<Class<?>>();
        set.add(Integer.TYPE);
        set.add(Long.TYPE);
        set.add(Float.TYPE);
        set.add(Double.TYPE);
        primitiveWideningsMap.put(Short.TYPE, set);
        primitiveWideningsMap.put(Character.TYPE, set);

        set = new HashSet<Class<?>>();
        set.add(Long.TYPE);
        set.add(Float.TYPE);
        set.add(Double.TYPE);
        primitiveWideningsMap.put(Integer.TYPE, set);

        set = new HashSet<Class<?>>();
        set.add(Float.TYPE);
        set.add(Double.TYPE);
        primitiveWideningsMap.put(Long.TYPE, set);

        set = new HashSet<Class<?>>();
        set.add(Double.TYPE);
        primitiveWideningsMap.put(Float.TYPE, set);
    }

    static {
        new ClassUtilities();
    }

    private ClassUtilities() {
        // nothing to do here
    }

    static Class<?> classForNameOrPrimitive(String name, ClassLoader loader) throws ClassNotFoundException {
        if (name == null || "".equals(name) || "null".equals(name) || "void".equals(name))
            return Void.TYPE;
        if ("boolean".equals(name))
            return Boolean.TYPE;
        if ("byte".equals(name))
            return Byte.TYPE;
        if ("char".equals(name))
            return Character.TYPE;
        if ("double".equals(name))
            return Double.TYPE;
        if ("float".equals(name))
            return Float.TYPE;
        if ("int".equals(name))
            return Integer.TYPE;
        if ("long".equals(name))
            return Long.TYPE;
        if ("short".equals(name))
            return Short.TYPE;

        return Class.forName(name, false, loader);
    }

    static boolean classIsAccessible(Class<?> aClass) {
        return aClass != null && isPublic(aClass.getModifiers());
    }

    static boolean compatibleClasses(Class<?>[] formals, Class<?>[] actuals) {
        if (formals.length != actuals.length)
            return false;

        for (int i = 0; i < formals.length; ++i) {
            if (actuals[i] == null || Void.TYPE.equals(actuals[i])) {
                if (formals[i].isPrimitive())
                    return false;
                continue;
            }

            if (!formals[i].isAssignableFrom(actuals[i])) {
                Class<?> formalPrimEquiv = primitiveEquivalentOf(formals[i]);
                Class<?> actualPrimEquiv = primitiveEquivalentOf(actuals[i]);

                if (!primitiveIsAssignableFrom(formalPrimEquiv, actualPrimEquiv))
                    return false;
            }
        }

        return true;
    }

    static Method getAccessibleMethodFrom(Class<?> aClass, String methodName, Class<?>[] parameterTypes) {
        Class<?> superclass = aClass.getSuperclass();
        Method overriddenMethod;

        if (classIsAccessible(superclass)) {
            try {
                overriddenMethod = superclass.getMethod(methodName, parameterTypes);
            }
            catch (NoSuchMethodException ignored) {
                overriddenMethod = null;
            }

            if (overriddenMethod != null)
                return overriddenMethod;
        }

        Class<?>[] interfaces = aClass.getInterfaces();

        for (int i = 0; i < interfaces.length; ++i) {
            if (classIsAccessible(interfaces[i])) {
                try {
                    overriddenMethod = interfaces[i].getMethod(methodName, parameterTypes);
                }
                catch (NoSuchMethodException ignored) {
                    overriddenMethod = null;
                }

                if (overriddenMethod != null)
                    return overriddenMethod;
            }
        }

        if (superclass != null) {
            overriddenMethod = getAccessibleMethodFrom(superclass, methodName, parameterTypes);

            if (overriddenMethod != null)
                return overriddenMethod;
        }

        for (int i = 0; i < interfaces.length; ++i) {
            overriddenMethod = getAccessibleMethodFrom(interfaces[i], methodName, parameterTypes);

            if (overriddenMethod != null)
                return overriddenMethod;
        }

        return null;
    }

    static Class<?> primitiveEquivalentOf(Class<?> aClass) {
        return aClass.isPrimitive() ? aClass : objectToPrimitiveMap.get(aClass);
    }

    static boolean primitiveIsAssignableFrom(Class<?> assigned, Class<?> assignee) {
        if (assigned == null || assignee == null)
            return false;

        if (!(assigned.isPrimitive() && assignee.isPrimitive()))
            return false;

        if (assigned.equals(assignee))
            return true;

        Set<Class<?>> wideningSet = primitiveWideningsMap.get(assignee);
        if (wideningSet == null)
            return false;

        return wideningSet.contains(assigned);
    }
}
